/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "gdt.h"

#include <string.h>
#include <machine/machine.h>
#include <machine/pc/processor/processor.h>
#include <kerneldebugconsole.h>

// These will all be safe for use when entering a double fault handler
static char g_SafeStack[8192] = {0};

PLGDT::PLGDT() {
}

void PLGDT::init() {
	m_descriptorCount = 6 + PLMachine::instance().processorCount();
	m_gdt = new segmentDescriptor[m_descriptorCount];
	setSegmentDescriptor(0, 0, 0, 0, 0);
	setSegmentDescriptor(1, 0, 0xFFFFF, 0x98, 0xC); // Kernel code - 0x08
	setSegmentDescriptor(2, 0, 0xFFFFF, 0x92, 0xC); // Kernel data - 0x10
	setSegmentDescriptor(3, 0, 0xFFFFF, 0xF8, 0xC); // User   code - 0x18
	setSegmentDescriptor(4, 0, 0xFFFFF, 0xF2, 0xC); // User   data - 0x20
	size_t i;
	for(i = 0 ; i < PLMachine::instance().processorCount(); i++) {
		TaskStateSegment *tss = new TaskStateSegment;
		initialiseTss(tss);
		setTssDescriptor(5 + i, reinterpret_cast<uint32_t>(tss));
		PLProcessor::instance().processorState(i).setTaskStateSegment(tss);
		PLProcessor::instance().processorState(i).setTaskStateSegmentSelector((i + 5) << 3);
	}
	// Create the double-fault handler TSS
	static TaskStateSegment dFTss;
	initialiseDoubleFaultTss(&dFTss);
	setTssDescriptor(5 + i + 1, reinterpret_cast<uint32_t>(&dFTss));
}

void PLGDT::set() {
	struct {
		uint16_t size;
		uint32_t gdt;
	} PACKED gdtr = { static_cast<uint16_t>((m_descriptorCount * 8 - 1) & 0xFFFF), reinterpret_cast<uintptr_t>(m_gdt) };
	asm volatile("lgdt %0" :: "m"(gdtr));
	uint16_t selector = PLProcessor::instance().processorState(0).taskStateSegmentSelector();
	PLKernelDebugConsole::instance().printf(". ");
	asm volatile("ltr %0": "=m" (selector));
	asm volatile("jmp $0x8, $.flush; \
					  .flush: \
					  mov $0x10, %ax; \
					  mov %ax, %ds; \
					  mov %ax, %es; \
					  mov %ax, %fs; \
					  mov %ax, %gs; \
					  mov %ax, %ss;");
}

void PLGDT::setSegmentDescriptor(size_t index, uint32_t base, uint32_t limit, uint8_t flags, uint8_t flags2) {
	m_gdt[index].limit0 = limit & 0xFFFF;
	m_gdt[index].base0  = base  & 0xFFFF;
	m_gdt[index].base1  = (base >> 16) & 0xFF;
	m_gdt[index].flags  = flags;
	m_gdt[index].flagsLimit1 = ((flags2 & 0x0F) << 4) | ((limit >> 16) & 0x0F);
	m_gdt[index].base2 = (base >> 24) & 0xFF;
}

void PLGDT::initialiseTss(TaskStateSegment *pTss) {
	memset(reinterpret_cast<void *>(pTss), 0, sizeof(TaskStateSegment));
	pTss->ss0 = 0x10;
}

void PLGDT::setTssDescriptor(size_t index, uint32_t base) {
	setSegmentDescriptor(index, base, sizeof(TaskStateSegment), 0x89, 0x00);
}

struct linearAddress {
	uint32_t offset : 12;
	uint32_t tbl : 10;
	uint32_t dir : 10;
} PACKED;

void PLGDT::initialiseDoubleFaultTss(TaskStateSegment *pTss) {
	// All our interrupt handlers - the TSS will hook into the DF handler, and the entry point
	extern uintptr_t interrupt_handler_array[];
	extern void start();
	memset( reinterpret_cast<void*>(pTss), 0, sizeof(TaskStateSegment) );
	// Stack - set to the statically allocated stack (mapped later)
	pTss->ss0 = pTss->ss = 0x10;
	pTss->esp0 = pTss->esp = pTss->esp1 = pTss->esp2 = reinterpret_cast<uint32_t>(g_SafeStack) + 8192;
	// When the fault occurs, jump to the ISR handler (so it'll automatically jump to the common ISR handler)
	pTss->eip = static_cast<uint32_t>(interrupt_handler_array[8]);
	// Set up the segments
	pTss->ds = pTss->es = pTss->fs = pTss->gs = 0x10;
	pTss->cs = 0x08;
	// Grab the kernel address space and clone it
	/// \note This doesn't seem to work??? I've used the current CR3 for now, but it's probably
	///       best to create a new PD for the DF handler.
	//pTss->cr3 = Processor::readCr3();
}
